#include "spi_soft.h"

#define SCK_H SPI_PORT->BSRR = SPI_SCK_PIN
#define SCK_L SPI_PORT->BRR = SPI_SCK_PIN
#define MOSI_H SPI_PORT->BSRR = SPI_MOSI_PIN
#define MOSI_L SPI_PORT->BRR = SPI_MOSI_PIN
#define MISO_READ ((SPI_PORT->IDR & SPI_MISO_PIN) ? 1 : 0)

/**
 * 初始化软件 SPI 接口。
 *
 * 功能描述：
 * 1. 使能 SPI 模块的时钟。
 * 2. 配置 SCK 和 MOSI 引脚为推挽输出模式，速度为 50MHz。
 * 3. 配置 MISO 引脚为上拉输入模式，速度为 50MHz。
 * 4. 初始化时钟线（SCK）为低电平。
 */
void spi_init(void)
{
    RCC_APB2PeriphClockCmd(SPI_RCC, ENABLE);

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = SPI_SCK_PIN | SPI_MOSI_PIN; // SCK MOSI
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;          // SCK MOSI 初始化为推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SPI_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = SPI_MISO_PIN;   // MISO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // MISO 初始化为上拉输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SPI_PORT, &GPIO_InitStructure);

    SCK_L; // 初始化时钟线为低电平
}

/**
 * 通过软件 SPI 进行字节数据交换（模式 0）。
 *
 * 功能描述：
 * 1. 使用 SPI 的模式 0（时钟极性为 0，时钟相位为 0）进行数据传输。
 * 2. 将输入的字节数据逐位发送到 MOSI 线，并从 MISO 线接收数据。
 * 3. 数据在时钟上升沿移出，在下降沿准备下一位数据。
 *
 * 参数：
 * - data：需要发送的字节数据。
 *
 * 返回值：
 * - 从 MISO 线接收到的字节数据。
 */
uint8_t spi_swapByte(uint8_t data)
{
    uint8_t i, byte = 0x00; // 用于存储接收到的数据

    for (i = 0; i < 8; i++)
    {
        // 根据当前位的值设置 MOSI 线状态
        if (data & 0x80)
        {
            MOSI_H; // 如果当前位为 1，则将 MOSI 线拉高
        }
        else
        {
            MOSI_L; // 如果当前位为 0，则将 MOSI 线拉低
        }

        data <<= 1; // 左移数据，准备下一位

        SCK_H; // 拉高时钟线，触发上升沿传输数据

        // 读取 MISO 线的状态并更新接收到的数据
        byte = (byte << 1) | MISO_READ;

        SCK_L; // 拉低时钟线，确保时钟处于低电平
    }
    return byte; // 返回接收到的数据
}
